package com.haha.qqchannelbot.start;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.haha.qqchannelbot.core.common.BotEventType;
import com.haha.qqchannelbot.core.common.BotIntent;
import com.haha.qqchannelbot.core.entity.Payload;
import com.haha.qqchannelbot.core.entity.User;
import com.haha.qqchannelbot.core.entity.dateCollect.AuthenticationDate;
import com.haha.qqchannelbot.core.entity.dateCollect.ChannelDate;
import com.haha.qqchannelbot.core.entity.dateCollect.GuildMessageDate;
import com.haha.qqchannelbot.core.entity.dateCollect.HelloDate;
import com.haha.qqchannelbot.core.entity.dateCollect.ReadyDate;
import com.haha.qqchannelbot.core.enums.ActionType;
import com.haha.qqchannelbot.core.enums.Opcode;
import com.haha.qqchannelbot.core.event.ActionEvent;
import com.haha.qqchannelbot.entity.Bot;
import com.haha.qqchannelbot.entity.BotMessage;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;

@Log4j2
@Service
public class BotSocketHandler implements WebSocketHandler {

    private WebSocketSession session;

    @Resource
    private Bot bot;

    @Resource
    private ObjectMapper objectMapper;

    @Resource
    private ApplicationEventPublisher eventPublisher;

    @Override
    public void afterConnectionEstablished(@NonNull WebSocketSession session) throws Exception {
        //连接建立
        log.info("连接建立");
        this.session = session;
    }

    @Override
    public void handleMessage(@NonNull WebSocketSession session, @NonNull WebSocketMessage<?> message) throws Exception {

        //接收消息
        Object payload1 = message.getPayload();
        log.info("接收消息:{}", payload1);
        this.session = session;

        //解析消息
//        Map<String, Object> payload = new JSONObject(message.getPayload()).toBean(Map.class);
        Payload payload = objectMapper.readValue(payload1.toString(), Payload.class);
        JsonNode jsonNode = objectMapper.readTree(payload1.toString());

        JsonNode jsonNodeD = jsonNode.get("d");
        payload.setD(jsonNodeD);

        Opcode opcode = Opcode.getByCode(payload.getOp());

        switch (opcode) {
            case HELLO:
                HelloDate helloDate = objectMapper.treeToValue(
                        jsonNodeD, HelloDate.class);
                payload.setD(helloDate);
                helloOperateHandler(payload);
                break;
            case DISPATCH:
                dispatchOperateHandler(payload);
                break;
            case HEARTBEAT_ACK:
                heartbeatAckOperateHandler();
                break;
            case RECONNECT:
                //动作重复，重复鉴权可能会导致鉴权失败
//                reconnectionOperateHandler();
                break;
            case INVALID:
                log.error("\n\n" +
                        " .----------------.  .----------------.  .----------------.  .----------------.  .----------------. \n" +
                        "| .--------------. || .--------------. || .--------------. || .--------------. || .--------------. |\n" +
                        "| |  _________   | || |  _______     | || |  _______     | || |     ____     | || |  _______     | |\n" +
                        "| | |_   ___  |  | || | |_   __ \\    | || | |_   __ \\    | || |   .'    `.   | || | |_   __ \\    | |\n" +
                        "| |   | |_  \\_|  | || |   | |__) |   | || |   | |__) |   | || |  /  .--.  \\  | || |   | |__) |   | |\n" +
                        "| |   |  _|  _   | || |   |  __ /    | || |   |  __ /    | || |  | |    | |  | || |   |  __ /    | |\n" +
                        "| |  _| |___/ |  | || |  _| |  \\ \\_  | || |  _| |  \\ \\_  | || |  \\  `--'  /  | || |  _| |  \\ \\_  | |\n" +
                        "| | |_________|  | || | |____| |___| | || | |____| |___| | || |   `.____.'   | || | |____| |___| | |\n" +
                        "| |              | || |              | || |              | || |              | || |              | |\n" +
                        "| '--------------' || '--------------' || '--------------' || '--------------' || '--------------' |\n" +
                        " '----------------'  '----------------'  '----------------'  '----------------'  '----------------' \n");
                log.error("鉴权失败， 参数错误");
                System.exit(1);
            default:
                log.info("不识别的指令");
                break;
        }
    }

    private void heartbeatAckOperateHandler() {
        log.info("收到心跳回复");
    }

    @Override
    public void handleTransportError(@NonNull WebSocketSession session, @NonNull Throwable exception) throws Exception {
        //异常处理
        System.out.println("异常处理");
    }

    @Override
    public void afterConnectionClosed(@NonNull WebSocketSession session, @NonNull CloseStatus closeStatus) throws Exception {
        //连接关闭
        log.info("服务端通知客户端重连");
        this.bot.setIsLogin(false);
        eventPublisher.publishEvent(ActionEvent.builder()
                .actionType(ActionType.RECONNECTION)
                .build());
    }

    /**
     * 事件调度
     *
     * @param payload
     */
    private void dispatchOperateHandler(Payload payload) throws JsonProcessingException {
        Integer seq = payload.getS();
//        if (seq != null && seq > 0) {
//            this.bot.setSeq(seq);
//        }
        JsonNode jsonNodeD = (JsonNode) payload.getD();
        String event = payload.getT();
        switch (event) {
            case BotEventType.READY -> {
                ReadyDate d = objectMapper.treeToValue(jsonNodeD, ReadyDate.class);
                User user = d.getUser();

                this.bot.setBotId(user.getId());
                this.bot.setBotName(user.getUsername());
                this.bot.setBotSessionId(d.getSessionId());

                if (this.bot.getIsLogin()) {
                    handlerHeartbeatInterval();
                }
                this.bot.setIsLogin(true);
                log.info("================");
                log.info("机器人登录成功");
                log.info("================");
            }
            case BotEventType.EVENT_AT_MESSAGE_CREATE -> {
                log.info("AT_MESSAGE_CREATE :: {}", payload.getD().toString());
                GuildMessageDate messageDate = objectMapper.treeToValue(jsonNodeD, GuildMessageDate.class);
                BotMessage botMessage = new BotMessage();
                botMessage.setBot(bot);
                botMessage.setMessageDate(messageDate);
                botMessage.setPayload(payload);
                eventPublisher.publishEvent(botMessage);
            }
            case BotEventType.EVENT_CHANNEL_CREATE,
                    BotEventType.EVENT_CHANNEL_UPDATE,
                    BotEventType.EVENT_CHANNEL_DELETE -> {
                ChannelDate channelDate = objectMapper.treeToValue(jsonNodeD, ChannelDate.class);
                log.info("子频道事件：{}", objectMapper.writeValueAsString(channelDate));
            }
            default -> log.error("不识别的事件类型");
        }
    }

    @Override
    public boolean supportsPartialMessages() {
        //是否支持接收不完整的消息
        return false;
    }

    private void helloOperateHandler(Payload payload) {
        // 初始化心跳周期
        initHeartbeatInterval(payload);
        // 初始化频道列表
//        initGuildList();
        // 发送Socket授权信息
        sendSocketAuthInfo();
    }

    /**
     * 发送Socket授权信息
     */
    @SneakyThrows
    private void sendSocketAuthInfo() {

        synchronized (this.session) {
            Payload payload = new Payload();
            payload.setOp(Opcode.IDENTIFY.getCode());
            AuthenticationDate authenticationDate = new AuthenticationDate();
            //拼接监听的消息类型
            authenticationDate.setIntents(BotIntent.computeIntents(BotIntent.IntentPublicGuildMessage,
                    BotIntent.IntentGuilds, BotIntent.IntentInteraction));
            authenticationDate.setToken(bot.getToken());
            authenticationDate.setShard(new Integer[]{bot.getCurrentShared(), bot.getCountShared()});
            payload.setD(authenticationDate);
            this.session.sendMessage(new TextMessage(objectMapper.writeValueAsString(payload)));
        }
    }

    /**
     * 处理周期心跳
     */
    private void handlerHeartbeatInterval() {
        Thread heartbeatThread = new Thread(() -> {
            while (true) {

                Map<String, Object> map = new HashMap<>();
                map.put("op", 1);
                map.put("d", "1");

                try {
                    Thread.sleep(this.bot.getHeartbeatInterval());
                    if (this.bot.getIsLogin()) {
                        synchronized (this.session) {
                            this.session.sendMessage(new TextMessage(objectMapper.writeValueAsString(map)));
                        }
                    }
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                }
            }
        });
        heartbeatThread.setDaemon(true);
        heartbeatThread.setName("HeartbeatThread");
        heartbeatThread.start();
    }

    /**
     * 服务端通知客户端重连
     */
    private void reconnectionOperateHandler() {
        log.info("服务端通知客户端重连");
        this.bot.setIsLogin(false);
        eventPublisher.publishEvent(ActionEvent.builder()
                .actionType(ActionType.RECONNECTION)
                .build());
    }

    /**
     * 初始化心跳周期
     *
     * @param payload
     */
    private void initHeartbeatInterval(Payload payload) {
        log.info("initHeartbeatInterval ::: {} ", payload);
        Long interval = ((HelloDate) payload.getD()).getHeartbeatInterval();
        log.info("heartbeat_time:" + interval);
        long heartbeatInterval = new BigDecimal(interval)
                .divide(new BigDecimal(2), 0, RoundingMode.HALF_DOWN)
                .longValue();
        log.info("heartbeat_interval:" + heartbeatInterval);
        this.bot.setHeartbeatInterval(heartbeatInterval);

    }

}
